UPDATE: This post is about old version of r7rs-pffi which did not support callbacks. Callback support is now beign/already added and the code in the repository is updated to reflect this.
This post is related to my previous blog post Portable foreign function interface for R7RS small.
I thought I'd write a small example of how to use the library. In my previous post I tried to explain more about why the library exists in the first place. This time we are getting our hands dirty, I'll make this more like a tutorial.
Prerequisites
If you are on guix just run
guix shell guile curl
Scheme implementation
If you want to follow along you need to have r7rs-pffi supported Scheme implementation installed. At the time of writing these are:
I'll be instructing how to run the examples with each implementation, so it does not matter which one you choose. The code will be exactly same on all of them.
If you are not on linux, Sagittarius, Racket and Kawa also run on other platforms like Windows. For Kawa we are using the kawa.jar, which is inside the "lib" directory of the precompiled zip in here.
r7rs-pffi
Download the latest release from https://codeberg.org/r7rs-pffi/pffi/releases and move the directory named "retropikzel" into your projects root directory.
libcurl
Unix
On unix systems you need libcurl installed, on Debian based linuxes it can be installed with
apt install libcurl4
Windows
Download curl from https://curl.se/windows/, unzip it and move the bin/libcurl-x64.dll to your projects root directory with name "curl.dll". I used the 64 bit version.
The code
In your projects root directory create file called main.scm and add the following code snippets to it.
(import (scheme base)
(scheme write)
(retropikzel pffi v0-2-2 main))
The headers are for implementations that compile to C. Prefix "lib" is added to library name on unixes automatically so no need to have it here. With ".4" as additional versions on linux these filenames are looked for (from varied system directories):
- libcurl.so
- libcurl.so.4
on windows the library searches for
- curl.dll
- curl.dll.4
(define libcurl (pffi-shared-object-auto-load (list "curl/curl.h") ; Headers
(list ".") ; Additional search paths
"curl" ; The named of shared object without the lib prefix
(list ".4"))) ;Additional versions to search
The arguments for pffi-define are
- Scheme name which is used to call the function
- The loaded shared library
- The C name of the function as a symbol
- The return type of the function
- The argument types of function as list of symbols
(pffi-define curl-easy-init libcurl 'curl_easy_init 'pointer (list))
(pffi-define curl-easy-setopt libcurl 'curl_easy_setopt 'int (list 'pointer 'int 'pointer))
(pffi-define curl-easy-perform libcurl 'curl_easy_perform 'int (list 'pointer))
I had to make a small C file and compile it to print these out. Sometimes you can find the values straight from headers. This is a little bit cumbersome but there is a solution of which I will write in the future:
(define CURLOPT-FOLLOWLOCATION 52)
(define CURLOPT-URL 10002)
(define handle (curl-easy-init))
(define url (pffi-string->pointer "https://scheme.org"))
(define curl-code1 (curl-easy-setopt handle CURLOPT-FOLLOWLOCATION url))
Set the url.
(define curl-code2 (curl-easy-setopt handle CURLOPT-URL url))
(display curl-code1)
(newline)
(display curl-code2)
(newline)
On C code you would pass a callback function to read the content of the webpage returned by curls request. Unfortunately r7rs-pffi does not support callback functions, it might some day but I can not make any promises, so the webpage html code will be outputted to stdout by curl.
(define result (curl-easy-perform handle))
(display result)
(newline)
(import (scheme base)
(scheme write)
(retropikzel pffi v0-2-2 main))
(define libcurl (pffi-shared-object-auto-load (list "curl/curl.h") ; Headers
(list ".") ; Additional search paths
"curl" ; The named of shared object without the lib prefix
(list ".4"))) ;Additional versions to search
(pffi-define curl-easy-init libcurl 'curl_easy_init 'pointer (list))
(pffi-define curl-easy-setopt libcurl 'curl_easy_setopt 'int (list 'pointer 'int 'pointer))
(pffi-define curl-easy-perform libcurl 'curl_easy_perform 'int (list 'pointer))
(define CURLOPT-FOLLOWLOCATION 52)
(define CURLOPT-URL 10002)
(define handle (curl-easy-init))
(define url (pffi-string->pointer "https://scheme.org"))
(define curl-code1 (curl-easy-setopt handle CURLOPT-FOLLOWLOCATION url))
(define curl-code2 (curl-easy-setopt handle CURLOPT-URL url))
(display curl-code1)
(newline)
(display curl-code2)
(newline)
(display result)
(newline)
Running the code
Sagittarius
sash -L . main.scm
on windows use sash.exe, or most propably you need the full path of the exe.
Guile
guile -L . main.scm
Kawa
Java version 21 is used, the new java FFI might be little different on 22 so it might not work.
java --add-exports java.base/jdk.internal.foreign.abi=ALL-UNNAMED --add-exports java.base/jdk.internal.foreign.layout=ALL-UNNAMED --add-exports java.base/jdk.internal.foreign=ALL-UNNAMED --enable-native-access=ALL-UNNAMED --enable-preview -jar kawa.jar main.scm
on widows use java.exe
Racket
You need to have r7rs installed with:
raco pkg install r7rs
and then run:
racket -I r7rs --make -S . --script main.scm
Chicken
Install r7rs with:
chicken-install r7rs
and install libcurl, on debian and derivates:
apt install libcurl4-openssl-dev
copy the pffi files to projects root directory like this:
cp retropikzel/pffi/v0-2-2/main.sld retropikzel.pffi.v0-2-2.main.scm
cp retropikzel/pffi/v0-2-2/chicken.scm retropikzel.pffi.v0-2-2.chicken.scm
compile them:
csc -X r7rs -R r7rs -sJ retropikzel.pffi.v0-2-2.chicken.scm
csc -X r7rs -R r7rs -sJ retropikzel.pffi.v0-2-2.main.scm
and then compile the main program:
csc -X r7rs -R r7rs -L -lcurl main.scm
and then run it:
./main
Closing words
And thats how you use the r7rs-pffi to make same code work on multiple implementations. I'm working on a blog post about a tool to automatically generate bindings to C libraries to make this easier.
The whole code for this can be found in https://codeberg.org/r7rs-pffi/example-libcurl.